// Copyright 2009-2012 by contributors, MIT License
// vim: ts=4 sts=4 sw=4 expandtab

// Module systems magic dance
(function(definition) {
	// RequireJS
	if (typeof define == "function") {
		define(definition);
		// YUI3
	} else if (typeof YUI == "function") {
		YUI.add("es5-sham", definition);
		// CommonJS and <script>
	} else {
		definition();
	}
})(function() {

	var call = Function.prototype.call;
	var prototypeOfObject = Object.prototype;
	var owns = call.bind(prototypeOfObject.hasOwnProperty);

	// If JS engine supports accessors creating shortcuts.
	var defineGetter;
	var defineSetter;
	var lookupGetter;
	var lookupSetter;
	var supportsAccessors;
	if ((supportsAccessors = owns(prototypeOfObject, "__defineGetter__"))) {
		defineGetter = call.bind(prototypeOfObject.__defineGetter__);
		defineSetter = call.bind(prototypeOfObject.__defineSetter__);
		lookupGetter = call.bind(prototypeOfObject.__lookupGetter__);
		lookupSetter = call.bind(prototypeOfObject.__lookupSetter__);
	}

	// ES5 15.2.3.2
	// http://es5.github.com/#x15.2.3.2
	if (!Object.getPrototypeOf) {
		// https://github.com/kriskowal/es5-shim/issues#issue/2
		// http://ejohn.org/blog/objectgetprototypeof/
		// recommended by fschaefer on github
		Object.getPrototypeOf = function getPrototypeOf(object) {
			return object.__proto__
					|| (object.constructor
							? object.constructor.prototype
							: prototypeOfObject);
		};
	}

	// ES5 15.2.3.3
	// http://es5.github.com/#x15.2.3.3

	function doesGetOwnPropertyDescriptorWork(object) {
		try {
			object.sentinel = 0;
			return Object.getOwnPropertyDescriptor(object, "sentinel").value === 0;
		} catch (exception) {
			// returns falsy
		}
	}

	// check whether getOwnPropertyDescriptor works if it's given. Otherwise,
	// shim partially.
	if (Object.defineProperty) {
		var getOwnPropertyDescriptorWorksOnObject = doesGetOwnPropertyDescriptorWork(
				{});
		var getOwnPropertyDescriptorWorksOnDom = typeof document == "undefined"
				|| doesGetOwnPropertyDescriptorWork(document
						.createElement("div"));
		if (!getOwnPropertyDescriptorWorksOnDom
				|| !getOwnPropertyDescriptorWorksOnObject) {
			var getOwnPropertyDescriptorFallback = Object.getOwnPropertyDescriptor;
		}
	}

	if (!Object.getOwnPropertyDescriptor || getOwnPropertyDescriptorFallback) {
		var ERR_NON_OBJECT = "Object.getOwnPropertyDescriptor called on a non-object: ";

		Object.getOwnPropertyDescriptor = function getOwnPropertyDescriptor(
				object, property) {
			if ((typeof object != "object" && typeof object != "function")
					|| object === null) {
				throw new TypeError(ERR_NON_OBJECT + object);
			}

			// make a valiant attempt to use the real getOwnPropertyDescriptor
			// for I8's DOM elements.
			if (getOwnPropertyDescriptorFallback) {
				try {
					return getOwnPropertyDescriptorFallback.call(Object,
							object, property);
				} catch (exception) {
					// try the shim if the real one doesn't work
				}
			}

			// If object does not owns property return undefined immediately.
			if (!owns(object, property)) {
				return;
			}

			// If object has a property then it's for sure both `enumerable` and
			// `configurable`.
			var descriptor = {
				enumerable : true,
				configurable : true
			};

			// If JS engine supports accessor properties then property may be a
			// getter or setter.
			if (supportsAccessors) {
				// Unfortunately `__lookupGetter__` will return a getter even
				// if object has own non getter property along with a same named
				// inherited getter. To avoid misbehavior we temporary remove
				// `__proto__` so that `__lookupGetter__` will return getter
				// only
				// if it's owned by an object.
				var prototype = object.__proto__;
				object.__proto__ = prototypeOfObject;

				var getter = lookupGetter(object, property);
				var setter = lookupSetter(object, property);

				// Once we have getter and setter we can put values back.
				object.__proto__ = prototype;

				if (getter || setter) {
					if (getter) {
						descriptor.get = getter;
					}
					if (setter) {
						descriptor.set = setter;
					}
					// If it was accessor property we're done and return here
					// in order to avoid adding `value` to the descriptor.
					return descriptor;
				}
			}

			// If we got this far we know that object has an own property that
			// is
			// not an accessor so we set it as a value and return descriptor.
			descriptor.value = object[property];
			descriptor.writable = true;
			return descriptor;
		};
	}

	// ES5 15.2.3.4
	// http://es5.github.com/#x15.2.3.4
	if (!Object.getOwnPropertyNames) {
		Object.getOwnPropertyNames = function getOwnPropertyNames(object) {
			return Object.keys(object);
		};
	}

	// ES5 15.2.3.5
	// http://es5.github.com/#x15.2.3.5
	if (!Object.create) {

		// Contributed by Brandon Benvie, October, 2012
		var createEmpty;
		var supportsProto = Object.prototype.__proto__ === null;
		if (supportsProto || typeof document == 'undefined') {
			createEmpty = function() {
				return {
					"__proto__" : null
				};
			};
		} else {
			// In old IE __proto__ can't be used to manually set `null`, nor
			// does
			// any other method exist to make an object that inherits from
			// nothing,
			// aside from Object.prototype itself. Instead, create a new global
			// object and *steal* its Object.prototype and strip it bare. This
			// is
			// used as the prototype to create nullary objects.
			createEmpty = function() {
				var iframe = document.createElement('iframe');
				var parent = document.body || document.documentElement;
				iframe.style.display = 'none';
				parent.appendChild(iframe);
				iframe.src = 'javascript:';
				var empty = iframe.contentWindow.Object.prototype;
				parent.removeChild(iframe);
				iframe = null;
				delete empty.constructor;
				delete empty.hasOwnProperty;
				delete empty.propertyIsEnumerable;
				delete empty.isPrototypeOf;
				delete empty.toLocaleString;
				delete empty.toString;
				delete empty.valueOf;
				empty.__proto__ = null;

				function Empty() {
				}
				Empty.prototype = empty;
				// short-circuit future calls
				createEmpty = function() {
					return new Empty();
				};
				return new Empty();
			};
		}

		Object.create = function create(prototype, properties) {

			var object;
			function Type() {
			} // An empty constructor.

			if (prototype === null) {
				object = createEmpty();
			} else {
				if (typeof prototype !== "object"
						&& typeof prototype !== "function") {
					// In the native implementation `parent` can be `null`
					// OR *any* `instanceof Object`
					// (Object|Function|Array|RegExp|etc)
					// Use `typeof` tho, b/c in old IE, DOM elements are not
					// `instanceof Object`
					// like they are in modern browsers. Using `Object.create`
					// on DOM elements
					// is...err...probably inappropriate, but the native version
					// allows for it.
					throw new TypeError("Object prototype may only be an Object or null"); // same
																							// msg
																							// as
																							// Chrome
				}
				Type.prototype = prototype;
				object = new Type();
				// IE has no built-in implementation of `Object.getPrototypeOf`
				// neither `__proto__`, but this manually setting `__proto__`
				// will
				// guarantee that `Object.getPrototypeOf` will work as expected
				// with
				// objects created using `Object.create`
				object.__proto__ = prototype;
			}

			if (properties !== void 0) {
				Object.defineProperties(object, properties);
			}

			return object;
		};
	}

	// ES5 15.2.3.6
	// http://es5.github.com/#x15.2.3.6

	// Patch for WebKit and IE8 standard mode
	// Designed by hax <hax.github.com>
	// related issue: https://github.com/kriskowal/es5-shim/issues#issue/5
	// IE8 Reference:
	// http://msdn.microsoft.com/en-us/library/dd282900.aspx
	// http://msdn.microsoft.com/en-us/library/dd229916.aspx
	// WebKit Bugs:
	// https://bugs.webkit.org/show_bug.cgi?id=36423

	function doesDefinePropertyWork(object) {
		try {
			Object.defineProperty(object, "sentinel", {});
			return "sentinel" in object;
		} catch (exception) {
			// returns falsy
		}
	}

	// check whether defineProperty works if it's given. Otherwise,
	// shim partially.
	if (Object.defineProperty) {
		var definePropertyWorksOnObject = doesDefinePropertyWork({});
		var definePropertyWorksOnDom = typeof document == "undefined"
				|| doesDefinePropertyWork(document.createElement("div"));
		if (!definePropertyWorksOnObject || !definePropertyWorksOnDom) {
			var definePropertyFallback = Object.defineProperty, definePropertiesFallback = Object.defineProperties;
		}
	}

	if (!Object.defineProperty || definePropertyFallback) {
		var ERR_NON_OBJECT_DESCRIPTOR = "Property description must be an object: ";
		var ERR_NON_OBJECT_TARGET = "Object.defineProperty called on non-object: "
		var ERR_ACCESSORS_NOT_SUPPORTED = "getters & setters can not be defined "
				+ "on this javascript engine";

		Object.defineProperty = function defineProperty(object, property,
				descriptor) {
			if ((typeof object != "object" && typeof object != "function")
					|| object === null) {
				throw new TypeError(ERR_NON_OBJECT_TARGET + object);
			}
			if ((typeof descriptor != "object" && typeof descriptor != "function")
					|| descriptor === null) {
				throw new TypeError(ERR_NON_OBJECT_DESCRIPTOR + descriptor);
			}
			// make a valiant attempt to use the real defineProperty
			// for I8's DOM elements.
			if (definePropertyFallback) {
				try {
					return definePropertyFallback.call(Object, object,
							property, descriptor);
				} catch (exception) {
					// try the shim if the real one doesn't work
				}
			}

			// If it's a data property.
			if (owns(descriptor, "value")) {
				// fail silently if "writable", "enumerable", or "configurable"
				// are requested but not supported
				/*
				 * // alternate approach: if ( // can't implement these
				 * features; allow false but not true !(owns(descriptor,
				 * "writable") ? descriptor.writable : true) ||
				 * !(owns(descriptor, "enumerable") ? descriptor.enumerable :
				 * true) || !(owns(descriptor, "configurable") ?
				 * descriptor.configurable : true) ) throw new RangeError( "This
				 * implementation of Object.defineProperty does not " + "support
				 * configurable, enumerable, or writable." );
				 */

				if (supportsAccessors
						&& (lookupGetter(object, property) || lookupSetter(
								object, property))) {
					// As accessors are supported only on engines implementing
					// `__proto__` we can safely override `__proto__` while
					// defining
					// a property to make sure that we don't hit an inherited
					// accessor.
					var prototype = object.__proto__;
					object.__proto__ = prototypeOfObject;
					// Deleting a property anyway since getter / setter may be
					// defined on object itself.
					delete object[property];
					object[property] = descriptor.value;
					// Setting original `__proto__` back now.
					object.__proto__ = prototype;
				} else {
					object[property] = descriptor.value;
				}
			} else {
				if (!supportsAccessors) {
					throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED);
				}
				// If we got that far then getters and setters can be defined !!
				if (owns(descriptor, "get")) {
					defineGetter(object, property, descriptor.get);
				}
				if (owns(descriptor, "set")) {
					defineSetter(object, property, descriptor.set);
				}
			}
			return object;
		};
	}

	// ES5 15.2.3.7
	// http://es5.github.com/#x15.2.3.7
	if (!Object.defineProperties || definePropertiesFallback) {
		Object.defineProperties = function defineProperties(object, properties) {
			// make a valiant attempt to use the real defineProperties
			if (definePropertiesFallback) {
				try {
					return definePropertiesFallback.call(Object, object,
							properties);
				} catch (exception) {
					// try the shim if the real one doesn't work
				}
			}

			for (var property in properties) {
				if (owns(properties, property) && property != "__proto__") {
					Object.defineProperty(object, property,
							properties[property]);
				}
			}
			return object;
		};
	}

	// ES5 15.2.3.8
	// http://es5.github.com/#x15.2.3.8
	if (!Object.seal) {
		Object.seal = function seal(object) {
			// this is misleading and breaks feature-detection, but
			// allows "securable" code to "gracefully" degrade to working
			// but insecure code.
			return object;
		};
	}

	// ES5 15.2.3.9
	// http://es5.github.com/#x15.2.3.9
	if (!Object.freeze) {
		Object.freeze = function freeze(object) {
			// this is misleading and breaks feature-detection, but
			// allows "securable" code to "gracefully" degrade to working
			// but insecure code.
			return object;
		};
	}

	// detect a Rhino bug and patch it
	try {
		Object.freeze(function() {
				});
	} catch (exception) {
		Object.freeze = (function freeze(freezeObject) {
			return function freeze(object) {
				if (typeof object == "function") {
					return object;
				} else {
					return freezeObject(object);
				}
			};
		})(Object.freeze);
	}

	// ES5 15.2.3.10
	// http://es5.github.com/#x15.2.3.10
	if (!Object.preventExtensions) {
		Object.preventExtensions = function preventExtensions(object) {
			// this is misleading and breaks feature-detection, but
			// allows "securable" code to "gracefully" degrade to working
			// but insecure code.
			return object;
		};
	}

	// ES5 15.2.3.11
	// http://es5.github.com/#x15.2.3.11
	if (!Object.isSealed) {
		Object.isSealed = function isSealed(object) {
			return false;
		};
	}

	// ES5 15.2.3.12
	// http://es5.github.com/#x15.2.3.12
	if (!Object.isFrozen) {
		Object.isFrozen = function isFrozen(object) {
			return false;
		};
	}

	// ES5 15.2.3.13
	// http://es5.github.com/#x15.2.3.13
	if (!Object.isExtensible) {
		Object.isExtensible = function isExtensible(object) {
			// 1. If Type(O) is not Object throw a TypeError exception.
			if (Object(object) !== object) {
				throw new TypeError(); // TODO message
			}
			// 2. Return the Boolean value of the [[Extensible]] internal
			// property of O.
			var name = '';
			while (owns(object, name)) {
				name += '?';
			}
			object[name] = true;
			var returnValue = owns(object, name);
			delete object[name];
			return returnValue;
		};
	}

});
